/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package util;

import controllers.DatabaseController;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import listcontrol.RemoteFileList;
import network.FileServer;
import views.DownloadDetails;

/**
 * Represents the download of a incomplete file part
 * @author Victor
 */
public class PartDownload implements Runnable {

    private final IncompleteFile file;
    private final RemoteFileList peer;
    private long index;
    private byte[] hashMD5;
    private final DownloadProperties properties;
    private DownloadDetails detailsWindow;
    private DatabaseController dbController;

    public PartDownload(IncompleteFile file, DownloadProperties properties, DownloadDetails detailsWindow, RemoteFileList peer, long index, byte[] hash, DatabaseController dbController) {
        this.file = file;
        this.peer = peer;
        this.index = index;
        this.hashMD5 = hash;
        this.properties = properties;
        this.detailsWindow = detailsWindow;
        this.dbController = dbController;
    }

    @Override
    public void run() {
        int read = 0;
        int in = 0;
        try {
            Socket conn = new Socket(peer.getIp(), FileServer.FILE_TRANSFER_PORT);
            DataOutputStream output = new DataOutputStream(conn.getOutputStream());
            DataInputStream input = new DataInputStream(conn.getInputStream());
            byte[] request = this.makeRequestPacket();

            output.write(request);

            synchronized (properties) {
                detailsWindow.setDownloading((int) index);
            }

            // Waiting response
            boolean hasFile = input.readBoolean();

            if (!hasFile) {
                throw new IOException("Peer não possui arquivo.");
            }

            // Receiveing payload...
            byte[] payload = new byte[IncompleteFile.CHUNK_SIZE];
            int left = payload.length;
            while (in != payload.length) {
                read = input.read(payload, in, left);
                if (read == -1) {
                    break;
                }
                in += read;
                left = payload.length - in;
                synchronized (properties) {
                    properties.setWindow(properties.getWindow() + read);
                    this.properties.setAmoutDownloaded(this.properties.getAmoutDownloaded() + read);
                }
            }
            if (in != payload.length) {
                throw new SocketException("Reached the end of the stream without reveive all the data.");
            }

            // Free resources
            input.close();
            output.close();
            conn.close();

            // Write the buffered data into the file            

            int downloaded;
            if ((index + 1) * IncompleteFile.CHUNK_SIZE > file.getSize()) {
                downloaded = (int) (file.getSize()) % (int) (IncompleteFile.CHUNK_SIZE);
                synchronized (properties) {
                    this.properties.setAmoutDownloaded(this.properties.getAmoutDownloaded() - (IncompleteFile.CHUNK_SIZE - downloaded));
                }
            } else {
                downloaded = IncompleteFile.CHUNK_SIZE;
            }

            java.io.File part = new java.io.File(dbController.SHARED_FOLDER_PATH + "/temp/" + "part" + this.index + this.file.getName());
            FileOutputStream fos = new FileOutputStream(part);
            fos.write(payload, 0, downloaded);
            fos.close();

            synchronized (properties) {
                properties.setDownloadedParts(properties.getDownloadedParts() + 1);
                detailsWindow.setDownloaded((int) index);
            }

            //System.out.println("[" + index + "] " + "Buffer written to de disk.");

        } catch (SocketException ex) {
            System.err.println("SocketEx: " + ex.getMessage());
            synchronized (this.properties.getParts()) {
                Long I = new Long(index);
                this.properties.getParts().add(I); // If something went wrong returns the index to the priority queue
                this.detailsWindow.setEmpty((int) index);
            }
            synchronized (properties) {
                properties.setWindow(properties.getWindow() - in);
                this.properties.setAmoutDownloaded(this.properties.getAmoutDownloaded() - in);
            }
            synchronized(this.dbController) {
                if (this.dbController.removePeer(this.peer.getIp())) {
                    System.err.println("Peer removido.");
                }
            }
            //this.properties.setnConn(this.properties.getnConn()-1);
        } catch (IOException ex) {
            System.err.println("IOEx: " + ex.getMessage());
            synchronized (this.properties.getParts()) {
                Long I = new Long(index);
                this.properties.getParts().add(I); // If something went wrong returns the index to the priority queue                
                this.detailsWindow.setEmpty((int) index);
            }
            synchronized (properties) {
                properties.setWindow(properties.getWindow() - in);
                this.properties.setAmoutDownloaded(this.properties.getAmoutDownloaded() - in);
            }
        }

        detailsWindow.removePeer((int) index);

        synchronized (peer) {
            peer.setConnections(peer.getConnections() - 1); // Thread safe             
        }

        synchronized (properties) {
            properties.setnConn(properties.getnConn() - 1);
        }

        synchronized (file) {
            file.notify(); // Wakeup the download management thread
        }

    }

    public byte[] makeRequestPacket() {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        try {
            dos.write(hashMD5);
            dos.writeLong(index);

            dos.flush();

            return bos.toByteArray();
        } catch (IOException ex) {
            System.err.println("Erro na montagem do pacote de requisição.");
            return null;
        }

    }

    public static int transfer(DataInputStream input, byte[] buffer, int length) throws IOException{
        int in = 0;
        int read;
        int left = length;
        while (in != length) {
            read = input.read(buffer, in, left);
            if (read == -1) {
                throw new SocketException("Connection reset before the hole payload was received. (" + in + " out of " + length);                                
            }
            in += read;
            left = length - in;            
        }
        
        return in;
    }
}
